/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: cbus_message.c
 *
 * Purpose: implementation message helper function for cbus
 *
 * Developer:
 *   wen.gu , 2022-02-16
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/

#include "cbus/bus/cbus_message.h"

#include <string.h>
#include <stdlib.h>

#include "cbus/core/cbus_atomic.h"
#include "cbus/bus/cbus_endpoint.h"

#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/

#define MSG_BUF_RESERVE_SIZE (4)

/**
 * @brief message header like:
 *  struct _cbus_message_header
 *   {
 *       GU32 length;
 *       cbus_message_id message_id;
 *       cbus_session_id session_id;
 *       GU08 protocol_version;
 *       GU08 interface_version;
 *       GU08 message_type;
 *       GU08 return_code; 
 *   };
 * 
 */


#define MSG_BASE_SIZE (sizeof(struct _cbus_message) + MSG_BUF_RESERVE_SIZE + CBUS_MSG_HDR_SIZE)

#define MSG_OFFSET_BASE (sizeof(struct _cbus_message) + MSG_BUF_RESERVE_SIZE)



#define MSG_OFFSET_START_CODE        (MSG_OFFSET_BASE - 1)
#define MSG_OFFSET_LENGTH            (MSG_OFFSET_BASE)
#define MSG_OFFSET_MESSAGE_ID        (MSG_OFFSET_LENGTH + sizeof(GU32))
#define MSG_OFFSET_SESSION_ID        (MSG_OFFSET_MESSAGE_ID + sizeof(cbus_message_id))
#define MSG_OFFSET_PROTOCOL_VERSION  (MSG_OFFSET_SESSION_ID + sizeof(cbus_session_id))
#define MSG_OFFSET_INTERFACE_VERSION (MSG_OFFSET_PROTOCOL_VERSION + sizeof(GU08))
#define MSG_OFFSET_MESSAGE_TYPE      (MSG_OFFSET_INTERFACE_VERSION + sizeof(GU08))
#define MSG_OFFSET_RETURN_CODE       (MSG_OFFSET_MESSAGE_TYPE + sizeof(GU08))


#define MSG_GET_PAYLOAD_SIZE(msg_buf) (MSG_MEMBER_LENGTH(msg_buf) - CBUS_MSG_HDR_SIZE)
#define MSG_GET_DATA(msg_buf)         (((GBUF)msg_buf) + MSG_OFFSET_START_CODE)

#define MSG_MEMBER_START_CODE(msg_buf)        ((GBUF)msg_buf)[MSG_OFFSET_START_CODE]
#define MSG_MEMBER_LENGTH(msg_buf)            *((GU32*)(((GBUF)msg_buf) + MSG_OFFSET_LENGTH))
#define MSG_MEMBER_MESSAGE_ID(msg_buf)        *((cbus_message_id*)(((GBUF)msg_buf) + MSG_OFFSET_MESSAGE_ID))
#define MSG_MEMBER_SESSION_ID(msg_buf)        *((cbus_session_id*)(((GBUF)msg_buf) + MSG_OFFSET_SESSION_ID))
#define MSG_MEMBER_PROTOCOL_VERSION(msg_buf)  ((GBUF)msg_buf)[MSG_OFFSET_PROTOCOL_VERSION]
#define MSG_MEMBER_INTERFACE_VERSION(msg_buf) ((GBUF)msg_buf)[MSG_OFFSET_INTERFACE_VERSION]
#define MSG_MEMBER_MESSAGE_TYPE(msg_buf)      ((GBUF)msg_buf)[MSG_OFFSET_MESSAGE_TYPE]
#define MSG_MEMBER_RETURN_CODE(msg_buf)       ((GBUF)msg_buf)[MSG_OFFSET_RETURN_CODE]
#define MSG_MEMBER_PAYLOAD(msg_buf)            (((GBUF)msg_buf) + MSG_BASE_SIZE)
#define MSG_MEMBER_END_CODE(msg_buf) ((GBUF)msg_buf)[MSG_BASE_SIZE + MSG_GET_PAYLOAD_SIZE(msg_buf)]

#define MSG_REF_COUNT_INIT(msg_buf) ((struct _cbus_message*)msg_buf)->ref_count = 1

/**skip the member of 'length' */
#define MSG_HEADER_START(msg_buf) (((GBUF)msg_buf) + MSG_OFFSET_BASE)

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
#if 0
struct _cbus_message_header
{
    GU32 length;
    cbus_message_id message_id;
    cbus_session_id session_id;
    GU08 protocol_version;
    GU08 interface_version;
    GU08 message_type;
    GU08 return_code; /** the code, could be a error code return as response */    
};
#endif


struct _cbus_message
{
    cbus_atomic_int ref_count;
    cbus_endpoint_id endpoint_id;
    cbus_endpoint_t endpoint_ref;
    GPTR opaque;
};

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/


/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
cbus_message_t cbus_message_alloc(GU32 payload_size)
{
    GBUF msg_buf = (GBUF)malloc(MSG_BASE_SIZE + payload_size + 1); /** +1 is for store 'end code' */

    if (!msg_buf)
    {
        return NULL;
    }

    memset(msg_buf, 0, MSG_BASE_SIZE);

    MSG_REF_COUNT_INIT(msg_buf);
    MSG_MEMBER_START_CODE(msg_buf) = CBUS_MESSAGE_START_CODE;
    MSG_MEMBER_LENGTH(msg_buf) = CBUS_MSG_HDR_SIZE + payload_size;
    MSG_MEMBER_PROTOCOL_VERSION(msg_buf) = CBUS_MESSAGE_PROTOCOL_VERSION;
    MSG_MEMBER_END_CODE(msg_buf) = CBUS_MESSAGE_END_CODE;

    return msg_buf;
}

cbus_message_t cbus_message_alloc_response(cbus_message_t request_message, GU32 response_payload_size)
{
    if (!request_message)
    {
        return NULL;
    }
    
    cbus_message_t response_message = cbus_message_alloc(response_payload_size);

    if (response_message)
    {
        struct _cbus_message* req_msg = (struct _cbus_message*)request_message;
        struct _cbus_message* res_msg = (struct _cbus_message*)response_message;
        
        res_msg->endpoint_id = req_msg->endpoint_id;
        res_msg->endpoint_ref = req_msg->endpoint_ref;
        res_msg->opaque = req_msg->opaque;

        MSG_MEMBER_MESSAGE_ID(response_message)        = MSG_MEMBER_MESSAGE_ID(request_message);
        MSG_MEMBER_SESSION_ID(response_message)        = MSG_MEMBER_SESSION_ID(request_message);
        MSG_MEMBER_PROTOCOL_VERSION(response_message)  = MSG_MEMBER_PROTOCOL_VERSION(request_message);
        MSG_MEMBER_INTERFACE_VERSION(response_message) = MSG_MEMBER_INTERFACE_VERSION(request_message);
        MSG_MEMBER_MESSAGE_TYPE(response_message)      = cbus_message_get_response_type(MSG_MEMBER_MESSAGE_TYPE(request_message));  
        LOGI("[%s.%d]request message type: %s, response type: %s\n", __FUNCTION__, __LINE__,
           cbus_message_type_to_str(MSG_MEMBER_MESSAGE_TYPE(request_message)), cbus_message_type_to_str(MSG_MEMBER_MESSAGE_TYPE(response_message)));    
    }

    return response_message;
}

cbus_message_t cbus_message_reference(cbus_message_t message)
{
    if (!message)
    {
        return NULL;
    }

    cbus_atomic_increment(&((struct _cbus_message*)message)->ref_count);

    return message; 
}

void cbus_message_free(cbus_message_t message)
{
    if (message)
    {
        if (cbus_atomic_decrement(&((struct _cbus_message*)message)->ref_count) == 1)
        {
            free(message);
        }
    }
}

void cbus_message_set_message_id(cbus_message_t message, cbus_message_id msg_id)
{
    MSG_MEMBER_MESSAGE_ID(message) = msg_id;
}

void cbus_message_set_session_id(cbus_message_t message, cbus_session_id session_id)
{
    MSG_MEMBER_SESSION_ID(message) = session_id;
}

void cbus_message_set_message_type(cbus_message_t message, cbus_message_type_t msg_type)
{
    MSG_MEMBER_MESSAGE_TYPE(message) = msg_type;
}

void cbus_message_set_protocol_version(cbus_message_t message, GU08 protocol_version)
{
    MSG_MEMBER_PROTOCOL_VERSION(message) = protocol_version;
}

void cbus_message_set_interface_version(cbus_message_t message, GU08 interface_version)
{
    MSG_MEMBER_INTERFACE_VERSION(message) = interface_version;
}

void cbus_message_set_return_code(cbus_message_t message, GU08 return_code)
{
    MSG_MEMBER_RETURN_CODE(message) = return_code;
}

void cbus_message_set_endpoint_id(cbus_message_t message, cbus_endpoint_id endpoint_id)
{
    ((struct _cbus_message*)message)->endpoint_id = endpoint_id;
}

void cbus_message_set_endpoint_ref(cbus_message_t message, cbus_endpoint_t endpoint_ref)
{
    ((struct _cbus_message*)message)->endpoint_ref = endpoint_ref;
}

void cbus_message_set_opaque(cbus_message_t message, GPTR opaque)
{
    ((struct _cbus_message*)message)->opaque = opaque;
}

void cbus_message_set_start_code(cbus_message_t message, GU08 start_code)
{
    MSG_MEMBER_START_CODE(message) = start_code;
}

void cbus_message_set_end_code(cbus_message_t message, GU08 end_code)
{
    MSG_MEMBER_END_CODE(message) = end_code;
}

cbus_message_id cbus_message_get_message_id(cbus_message_t message)
{
    return MSG_MEMBER_MESSAGE_ID(message);
}

cbus_session_id cbus_message_get_session_id(cbus_message_t message)
{
    return MSG_MEMBER_SESSION_ID(message);
}

cbus_message_type_t cbus_message_get_message_type(cbus_message_t message)
{
    return MSG_MEMBER_MESSAGE_TYPE(message);
}

GU08 cbus_message_get_protocol_version(cbus_message_t message)
{
    return MSG_MEMBER_PROTOCOL_VERSION(message);
}

GU08 cbus_message_get_interface_version(cbus_message_t message)
{
    return MSG_MEMBER_INTERFACE_VERSION(message);
}

GU08 cbus_message_get_return_code(cbus_message_t message)
{
    return MSG_MEMBER_RETURN_CODE(message);
}

cbus_endpoint_id cbus_message_get_endpoint_id(cbus_message_t message)
{
    return ((struct _cbus_message*)message)->endpoint_id;
}

cbus_endpoint_t cbus_message_get_endpoint_ref(cbus_message_t message)
{
    return ((struct _cbus_message*)message)->endpoint_ref;
}

GPTR cbus_message_get_opaque(cbus_message_t message)
{
    return ((struct _cbus_message*)message)->opaque;
}

GBUF cbus_message_get_data(cbus_message_t message)
{
    return MSG_GET_DATA(message);
}

GU32 cbus_message_get_size(cbus_message_t message)
{
    return MSG_MEMBER_LENGTH(message) + 2; /** +2 to include 'start code'  and  'end code' */
}

GBUF cbus_message_get_payload(cbus_message_t message)
{
    return MSG_MEMBER_PAYLOAD(message);
}

GU32 cbus_message_get_payload_size(cbus_message_t message)
{
    return MSG_GET_PAYLOAD_SIZE(message);
}

GU08 cbus_message_get_start_code(cbus_message_t message)
{
    return MSG_MEMBER_START_CODE(message);
}

GU08 cbus_message_get_end_code(cbus_message_t message)
{
    return MSG_MEMBER_END_CODE(message);
}

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

GErrc cbus_message_send(cbus_message_t message)
{
    return cbus_endpoint_send(cbus_message_get_endpoint_ref(message), message);
}

GErrc cbus_message_send_response_code(cbus_message_t request_message, GU08 return_code)
{
    if (!request_message || !cbus_message_get_endpoint_ref(request_message) || !cbus_message_get_endpoint_id(request_message))
    {
        return G_ErrBadParameter;
    }

    cbus_message_t response_message = cbus_message_alloc_response(request_message, 0);
    if (response_message)
    {
        cbus_message_set_return_code(response_message, return_code);
        GErrc ret = cbus_endpoint_send(cbus_message_get_endpoint_ref(response_message), response_message);
        cbus_message_free(response_message);
        return ret;
    }

    return G_ErrInsufficientResources;    
}

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
cbus_message_return_code_t cbus_gerrc_to_msg_return_code(GErrc errc)
{
    switch (errc)
    {
    case G_OK:                       return CBUS_MSG_OK;  break;
    case G_ErrInsufficientResources: return CBUS_MSG_ErrInsufficientResources;  break;
    case G_ErrNotFound:              return CBUS_MSG_ErrNotFound;  break;
    case G_ErrTimeout:               return CBUS_MSG_ErrTimeout; break;
    case G_ErrNotImplemented:        return CBUS_MSG_ErrUnknownMethod; break;
    case G_ErrVersionMismatch:       return CBUS_MSG_ErrWrongInterfaceVersion; break;
    default:
        break;
    }

    return CBUS_MSG_ErrUndefined;
}

GErrc cbus_msg_return_code_to_gerrc(cbus_message_return_code_t return_code)
{
    switch (return_code)
    {
    case CBUS_MSG_OK:                       return G_OK;  break;
    case CBUS_MSG_ErrInsufficientResources: return G_ErrInsufficientResources;  break;
    case CBUS_MSG_ErrNotFound:              return G_ErrNotFound;  break;
    case CBUS_MSG_ErrTimeout:               return G_ErrTimeout;  break;
    case CBUS_MSG_ErrUnknownMethod:         return G_ErrNotImplemented;  break;
    case CBUS_MSG_ErrWrongInterfaceVersion: return G_ErrVersionMismatch;  break;
    case CBUS_MSG_ErrWrongProtocolVersion:  return G_ErrVersionMismatch;  break;
    default:
        break;
    }

    return G_ErrUndefined;
}

GU08 cbus_message_get_response_type(GU08 request_message_type)
{
    switch (request_message_type)
    {
    case CBUS_MSG_TYPE_Method_Request:          return (GU08)CBUS_MSG_TYPE_Method_Response;             break;
    case CBUS_MSG_TYPE_Event_Subscribe:         return (GU08)CBUS_MSG_TYPE_Event_Subscribe_Ack;         break;
    case CBUS_MSG_TYPE_Event_Stop_Subscribe:    return (GU08)CBUS_MSG_TYPE_Event_Stop_Subscribe_Ack;    break;
    case CBUS_MSG_TYPE_Property_Get:            return (GU08)CBUS_MSG_TYPE_Property_Get_Ack;            break;
    case CBUS_MSG_TYPE_Property_Set:            return (GU08)CBUS_MSG_TYPE_Property_Set_Ack;            break;
    case CBUS_MSG_TYPE_Property_Subscribe:      return (GU08)CBUS_MSG_TYPE_Property_Subscribe_Ack;      break;
    case CBUS_MSG_TYPE_Property_Stop_Subscribe: return (GU08)CBUS_MSG_TYPE_Property_Stop_Subscribe_Ack; break;
    default:
        break;
    }

    return CBUS_MSG_TYPE_Unknown;
}

GU08 cbus_message_get_request_type(GU08 response_message_type)
{
    switch (response_message_type)
    {
    case CBUS_MSG_TYPE_Method_Response:             return (GU08)CBUS_MSG_TYPE_Method_Request;           break;
    case CBUS_MSG_TYPE_Event_Subscribe_Ack:         return (GU08)CBUS_MSG_TYPE_Event_Subscribe;          break;
    case CBUS_MSG_TYPE_Event_Stop_Subscribe_Ack:    return (GU08)CBUS_MSG_TYPE_Event_Stop_Subscribe;     break;
    case CBUS_MSG_TYPE_Property_Get_Ack:            return (GU08)CBUS_MSG_TYPE_Property_Get;             break;
    case CBUS_MSG_TYPE_Property_Set_Ack:            return (GU08)CBUS_MSG_TYPE_Property_Set;             break;
    case CBUS_MSG_TYPE_Property_Subscribe_Ack:      return (GU08)CBUS_MSG_TYPE_Property_Subscribe;       break;
    case CBUS_MSG_TYPE_Property_Stop_Subscribe_Ack: return (GU08)CBUS_MSG_TYPE_Property_Stop_Subscribe;  break;
    default:
        break;
    }

    return CBUS_MSG_TYPE_Unknown;
}

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////

const GSTR cbus_message_type_to_str(cbus_message_type_t msg_type)
{
#define TYPE_STR_FIELD(type) case type: return #type ; break
    switch (msg_type)
    {
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Method_Request);
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Method_Request_No_Return); 
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Method_Response);
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Event);
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Event_Subscribe);
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Event_Subscribe_Group); 
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Event_Subscribe_Ack ); 
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Event_Stop_Subscribe); 
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Event_Stop_Subscribe_Ack); 
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Property_Set );
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Property_Set_Ack); 
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Property_Get);
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Property_Get_Ack); 
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Property_Notification); 
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Property_Subscribe);         
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Property_Subscribe_Ack);     
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Property_Stop_Subscribe);     
    TYPE_STR_FIELD(CBUS_MSG_TYPE_Property_Stop_Subscribe_Ack);     
    default:
        break;
    }

    return "unknown";
}
